把一堆散落的檔案,一鍵整理到結構化的資料夾:
模式一:依 副檔名 分類(例:jpg/ png/ pdf/ ...)
模式二:依 日期 分類(YYYY/MM/,用最後修改時間)
支援 move/copy、遞迴、萬用字元過濾
預設 Dry-run(只做計畫、不動檔)+輸出 CSV 日誌
若目標檔名已存在 → 自動加序號避免覆蓋
程式碼(存成 file_sorter.py)
# file_sorter.py — Day 17:依副檔名/日期歸檔(move/copy/試跑/CSV 紀錄)
from __future__ import annotations
import argparse, csv, shutil, time
from pathlib import Path
from typing import Iterable, List, Tuple
def iter_files(root: Path, recursive: bool, patterns: List[str] | None) -> Iterable[Path]:
pats = patterns or ["*"]
seen = set()
for pat in pats:
glob = root.rglob if recursive else root.glob
for p in glob(pat):
if p.is_file():
rp = p.resolve()
if rp not in seen:
seen.add(rp)
yield p
def date_folder(p: Path) -> Path:
st = p.stat()
y = time.strftime("%Y", time.localtime(st.st_mtime))
m = time.strftime("%m", time.localtime(st.st_mtime))
return Path(y) / m # e.g. 2025/09
def ext_folder(p: Path) -> Path:
ext = p.suffix.lower().lstrip(".") or "_noext"
return Path(ext)
def unique_path(dst: Path) -> Tuple[Path, str]:
"""避免覆蓋:若存在就自動加 _1, _2...,回傳(路徑, 說明文字)"""
if not dst.exists():
return dst, ""
base, ext = dst.stem, dst.suffix
k = 1
while True:
alt = dst.with_name(f"{base}_{k}{ext}")
if not alt.exists():
return alt, f"rename({_kstr(k)})"
k += 1
def _kstr(k: int) -> str:
return str(k)
def write_csv(rows: List[dict], out: Path):
out.parent.mkdir(parents=True, exist_ok=True)
cols = ["action","src","dst","note"]
with out.open("w", encoding="utf-8", newline="") as f:
w = csv.DictWriter(f, fieldnames=cols)
w.writeheader()
for r in rows: w.writerow(r)
def main():
ap = argparse.ArgumentParser(description="檔案歸檔器:依副檔名或日期分類(支援 move/copy/試跑/CSV)")
ap.add_argument("--src", type=Path, required=True, help="來源資料夾")
ap.add_argument("--dst", type=Path, required=True, help="目標資料夾")
ap.add_argument("--by", choices=["ext","date"], default="ext", help="分類方式:ext=副檔名;date=YYYY/MM")
ap.add_argument("--mode", choices=["move","copy"], default="move", help="動作:move 或 copy(預設 move)")
ap.add_argument("--recursive", action="store_true", help="包含子資料夾")
ap.add_argument("--match", nargs="*", help="萬用字元過濾,如 '*.jpg' '*.pdf'")
ap.add_argument("--dry-run", action="store_true", help="試跑:只列出計畫,不真的移動/複製(預設開啟)")
ap.add_argument("--apply", action="store_true", help="真的執行(與 --dry-run 互斥,二擇一)")
ap.add_argument("--out", type=Path, default=Path("exports/sort_log.csv"), help="輸出 CSV 日誌路徑")
args = ap.parse_args()
if args.dry_run and args.apply:
print("❌ --dry-run 與 --apply 不能同時使用"); return
if not args.apply:
args.dry_run = True # 預設試跑
src, dst = args.src, args.dst
if not src.exists():
print("❌ 來源不存在"); return
dst.mkdir(parents=True, exist_ok=True)
files = list(iter_files(src, args.recursive, args.match))
if not files:
print("⚠️ 找不到檔案(檢查 --match / 路徑)"); return
rows: List[dict] = []
count = 0
for p in files:
sub = ext_folder(p) if args.by == "ext" else date_folder(p)
out_dir = dst / sub
out_dir.mkdir(parents=True, exist_ok=True)
target = out_dir / p.name
final, note = unique_path(target)
if args.dry_run:
rows.append({"action": f"plan-{args.mode}", "src": str(p), "dst": str(final), "note": note})
else:
try:
if args.mode == "move":
shutil.move(str(p), str(final))
else:
shutil.copy2(str(p), str(final))
rows.append({"action": args.mode, "src": str(p), "dst": str(final), "note": note})
count += 1
except Exception as e:
rows.append({"action": "error", "src": str(p), "dst": str(final), "note": str(e)})
write_csv(rows, args.out)
if args.dry_run:
print(f"📝 試跑完成,{len(rows)} 筆計畫已輸出:{args.out}")
else:
print(f"✅ 已{args.mode} {count} 個檔案;明細:{args.out}")
if __name__ == "__main__":
main()
怎麼用(PowerShell)
# 1) 先「試跑」:目前資料夾的 PDF/JPG,依副檔名分類到 .\sorted\
python .\file_sorter.py --src . --dst .\sorted --by ext --recursive --match '*.pdf' '*.jpg'
ii .\exports # 看 sort_log.csv 計畫
# 2) 真的執行(移動)
python .\file_sorter.py --src . --dst .\sorted --by ext --recursive --match '*.pdf' '*.jpg' --apply
# 3) 依「日期」分類(YYYY\MM),使用複製模式
python .\file_sorter.py --src . --dst .\backup --by date --recursive --mode copy --apply
# 4) 只整理目前資料夾(不含子資料夾)、所有檔案
python .\file_sorter.py --src . --dst .\sorted --by ext --apply
實作:
重點 & 小提醒
預設 Dry-run:不動檔案,只輸出 CSV 日誌(exports\sort_log.csv),安全先看計畫再決定。
衝突自動改名:目標檔已存在時自動加 _1、_2…,note 會標 rename(k)。
PowerShell 萬用字元要加引號:'.pdf'、'.jpg'。
--by date 使用檔案最後修改時間(mtime)決定 YYYY/MM 目錄。
今日小結
完成一支「檔案歸檔器」:副檔名/日期兩種分類法、支援 move/copy、試跑與 CSV 紀錄